#include <linux/buffer_head.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include "lab4fs.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Teams 3 and 4");

static void lab4fs_put_super(struct super_block *sb)
{
	struct lab4fs_sb_info *sbi;
	
	sbi = LAB4FS_SB(sb);
	if (sbi == NULL)
		return;
	
	mark_buffer_dirty(sbi->s_sbh);
	brelse(sbi->s_sbh);
	sb->s_fs_info = NULL;
	kfree(sbi);
	
	return;
}

struct super_operations lab4fs_super_ops = {
	.statfs = simple_statfs,
	.put_super = lab4fs_put_super,
};

/*
 * Maximal file size.  There is a direct block limit, and also a 
 * limit of (2^32 - 1) 512-byte sectors in i_blocks.
 * We need to be 1 filesystem block less than the 2^32 sector limit.
 */
static loff_t lab4fs_max_size(int bits)
{
	loff_t res = LAB4FS_NBLOCKS;
	res += 1LL << (bits-2);
	res <<= bits;
	if (res > (512LL << 32) - (1 << bits))
		res = (512LL << 32) - (1 << bits);
	return res;
}

static int lab4fs_fill_super(struct super_block *sb, void *data, int silent)
{
	struct buffer_head *bh;
	struct inode *root_inode;
	struct lab4fs_sb_info *sbi;
	struct lab4fs_super_block *l4s;
	
	sbi = kmalloc(sizeof(*sbi), GFP_KERNEL);
	if (!sbi)
		return -ENOMEM;
	sb->s_fs_info = sbi;
	memset(sbi, 0, sizeof(*sbi));
	
	if (!sb_set_blocksize(sb, BLOCK_SIZE))
		goto out_bad_hblock;
	
	// Read super block from disk
	if (!(bh = sb_bread(sb, 1)))
		goto out_bad_sb;
	
	l4s = (struct lab4fs_super_block *) bh->b_data;
	printk("lab4fs: l4s->s_magic = %d, l4s->s_blocks_count = %d\n", l4s->s_magic, l4s->s_blocks_count);
	sbi->s_l4s = l4s;
	sbi->s_sbh = bh;
	
	// Check magic num is correct for filesystem
	sb->s_magic = l4s->s_magic;
	if (sb->s_magic != LAB4FS_MAGIC)
		goto out_no_fs;
	
	sb->s_maxbytes = lab4fs_max_size(sb->s_blocksize_bits);
	printk("lab4fs: max filesize = %llu\n", sb->s_maxbytes);
	
	/* set up enough so that it can read an inode */
	sb->s_op = &lab4fs_super_ops;
	root_inode = iget(sb, LAB4FS_ROOT_INO);
	if (!root_inode || is_bad_inode(root_inode))
		goto out_no_root;
	
	sb->s_root = d_alloc_root(root_inode);
	if (!sb->s_root)
		goto out_iput;
	
	return 0;

out_iput:
	iput(root_inode);
	goto out_release;

out_no_root:
	if (!silent)
		printk("lab4fs: get root inode failed\n");
	goto out_release;
out_no_fs:
	if (!silent)
		printk("VFS: Can't find a lab4fs filesystem on device "
		       "%s.\n", sb->s_id);
    out_release:
	brelse(bh);
	goto out;
out_bad_hblock:
	printk("lab4fs: blocksize too small for device.\n");
	goto out;

out_bad_sb:
	printk("lab4fs: unable to read superblock\n");
 out:
	sb->s_fs_info = NULL;
	kfree(sbi);
	return -EINVAL;
}

struct super_block * lab4fs_get_sb(struct file_system_type *fs_type, int flags,
	const char *dev_name, void *data)
{
	return get_sb_bdev(fs_type, flags, dev_name, data, lab4fs_fill_super);
}

static struct file_system_type lab4fs_fs_type = {
	.owner = THIS_MODULE,
	.name = "lab4fs",
	.get_sb = lab4fs_get_sb,
	.kill_sb = kill_block_super,
	.fs_flags = FS_REQUIRES_DEV,
};

static int __init init_lab4_fs(void)
{
	printk("lab4fs: module loaded\n");
	return register_filesystem(&lab4fs_fs_type);
}

static void __exit exit_lab4_fs(void)
{
	unregister_filesystem(&lab4fs_fs_type);
	printk("lab4fs: module unloaded\n");
}

module_init(init_lab4_fs)
module_exit(exit_lab4_fs)
